package com.chuangyiruanke.util;

import com.sun.istack.internal.NotNull;

public class BitArray {

    /**
     * Values are stored in a byte array
     */
    protected byte[] bitmap;

    /**
     * Number of elements in bitmap
     */
    protected int size;

    @SuppressWarnings("unused")
    public BitArray() {
        this(128, false);
    }

    @SuppressWarnings("unused")
    public BitArray(int size) {
        this(size, false);
    }

    /**
     * Create a bitmap
     *
     * @param size         number of elements in bitmap
     * @param defaultValue true : set all available bits to 1
     *                     false : all available bits remain 0
     */
    @SuppressWarnings("unused")
    public BitArray(int size, boolean defaultValue) {
        int bytes = (int) Math.ceil((double) size / 8);//number of bytes
        bitmap = new byte[bytes];
        this.size = size;
        if (defaultValue) {
            for (int i = 0; i < bitmap.length; i++) {
                bitmap[i] = (byte) 0xFF;
            }
        }
    }

    @SuppressWarnings("unused")
    public BitArray(byte[] bitmap) {
        this.bitmap = bitmap;
        this.size = bitmap.length << 3; // length << BYTE_SHIFT
    }

    /**
     * Set the position on bitmap
     *
     * @param position position on bitmap
     * @param value    设置指定位的值
     */
    @SuppressWarnings("unused")
    public void set(int position, boolean value) {
        if (position > size) return;

        int bitOffset = position % 8;
        int byteOffset = getByteOffset(position);
        if (value) {
            bitmap[byteOffset] = (byte) (bitmap[byteOffset] | (0x80 >> bitOffset));
        } else {
            bitmap[byteOffset] = (byte) (bitmap[byteOffset] & ~(0x80 >> bitOffset));
        }
    }

    @NotNull
    @SuppressWarnings("unused")
    public BitArray setBitmap(byte[] bitmap) {
        if (bitmap == null) {
            size = 0;
            this.bitmap = new byte[0];
        } else {
            if (size != (bitmap.length << 3)) {
                size = bitmap.length << 3;
                this.bitmap = new byte[bitmap.length];
            }
            System.arraycopy(bitmap, 0, this.bitmap, 0, bitmap.length);
        }
        return this;
    }

    /**
     * Get the value of the position on bitmap
     *
     * @param position position on bitmap
     * @return true if the position is 1,false else
     */
    @SuppressWarnings("unused")
    public boolean get(int position) {
        if (position > size) return false;

        int bitOffset = position & 0x7;
        int byteOffset = getByteOffset(position);
        return (bitmap[byteOffset] & (0x80 >> bitOffset)) != 0;
    }

    @SuppressWarnings("unused")
    protected int getByteOffset(int position) {
        return position >> 3;
    }

    @NotNull
    @SuppressWarnings("unused")
    public BitArray not() {
        for (int i = 0; i < bitmap.length; i++)
            bitmap[i] = (byte) ~bitmap[i];
        return this;
    }

    @NotNull
    @SuppressWarnings("unused")
    public BitArray and(BitArray map) {
        BitArray result = new BitArray(0), other = null;
        if (map.size >= this.size) {
            other = this;
            result.setBitmap(map.bitmap);
        } else {
            other = map;
            result.setBitmap(bitmap);
        }
        byte[] byteArray = result.bitmap;
        for (int i = 0; i < byteArray.length; i++) {
            if (i < other.bitmap.length) {
                byteArray[i] &= other.bitmap[i];
            } else {
                byteArray[i] = 0;
            }
        }
        return result;
    }

    @NotNull
    @SuppressWarnings("unused")
    public BitArray or(BitArray map) {
        BitArray result = new BitArray(0), other = null;
        if (map.size >= this.size) {
            other = this;
            result.setBitmap(map.bitmap);
        } else {
            other = map;
            result.setBitmap(bitmap);
        }
        byte[] byteArray = result.bitmap;
        for (int i = 0; i < other.bitmap.length; i++) {
            byteArray[i] |= other.bitmap[i];
        }
        return result;
    }

    @NotNull
    @SuppressWarnings("unused")
    public BitArray xor(BitArray map) {
        BitArray result = new BitArray(0), other = null;
        if (map.size >= this.size) {
            other = this;
            result.setBitmap(map.bitmap);
        } else {
            other = map;
            result.setBitmap(bitmap);
        }
        byte[] byteArray = result.bitmap;
        for (int i = 0; i < other.bitmap.length; i++) {
            byteArray[i] ^= other.bitmap[i];
        }
        return result;
    }

    @NotNull
    @SuppressWarnings("unused")
    public BitArray nor(BitArray map) {
        BitArray result = new BitArray(0), other = null;
        if (map.size >= this.size) {
            other = this;
            result.setBitmap(map.bitmap);
        } else {
            other = map;
            result.setBitmap(bitmap);
        }
        byte[] byteArray = result.bitmap;
        for (int i = 0; i < byteArray.length; i++) {
            if (i < other.bitmap.length) {
                byteArray[i] = (byte) ~(byteArray[i] | other.bitmap[i]);
            } else {
                byteArray[i] = (byte) ~byteArray[i];
            }
        }
        return result;
    }

    @NotNull
    @SuppressWarnings("unused")
    public BitArray xnor(BitArray map) {
        BitArray result = new BitArray(0), other = null;
        if (map.size >= this.size) {
            other = this;
            result.setBitmap(map.bitmap);
        } else {
            other = map;
            result.setBitmap(bitmap);
        }
        byte[] byteArray = result.bitmap;
        for (int i = 0; i < byteArray.length; i++) {
            if (i < other.bitmap.length) {
                byteArray[i] = (byte) ~(byteArray[i] ^ other.bitmap[i]);
            } else {
                byteArray[i] = (byte) ~byteArray[i];
            }
        }
        return result;
    }

    @NotNull
    @SuppressWarnings("unused")
    public BitArray nand(BitArray map) {
        BitArray result = new BitArray(0), other = null;
        if (map.size >= this.size) {
            other = this;
            result.setBitmap(map.bitmap);
        } else {
            other = map;
            result.setBitmap(bitmap);
        }
        byte[] byteArray = result.bitmap;
        for (int i = 0; i < byteArray.length; i++) {
            if (i < other.bitmap.length) {
                byteArray[i] = (byte) ~(byteArray[i] & other.bitmap[i]);
            } else {
                byteArray[i] = (byte) 0xff;
            }
        }

        return result;
    }

    @SuppressWarnings("unused")
    public void getBitmap(@NotNull byte[] bitmap) {
        int length = Math.min(bitmap.length, this.bitmap.length);
        System.arraycopy(this.bitmap, 0, bitmap, 0, length);
    }

    public byte[] getBitmap() {
        return this.bitmap;
    }

    public int getSize() {
        return size;
    }
}
